﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Animation;
using System.ComponentModel;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Fournit des fonctionnalités pour les modèles 3-D. 
    /// </summary>
    public abstract class Model3D10 : Animatable, IDrawable
    {
        #region Dependency Properties
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Transform"/>.
        /// </summary>
        public static readonly DependencyProperty TransformProperty =
            DependencyProperty.Register("Transform", typeof(Transform3D10), typeof(Model3D10),
            new PropertyMetadata(new PropertyChangedCallback(Transform_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Effect"/>.
        /// </summary>
        public static readonly DependencyProperty EffectProperty =
            DependencyProperty.Register("Effect", typeof(SlimDX.Direct3D10.Effect), typeof(Model3D10),
            new PropertyMetadata(new PropertyChangedCallback(Effect_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="EffectTechnique"/>.
        /// </summary>
        public static readonly DependencyProperty EffectTechniqueProperty =
            DependencyProperty.Register("EffectTechnique", typeof(string), typeof(Model3D10),
            new PropertyMetadata(string.Empty, new PropertyChangedCallback(EffectTechnique_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="EffectParameters"/>.
        /// </summary>
        public static readonly DependencyProperty EffectParametersProperty =
            DependencyProperty.Register("EffectParameters", typeof(EffectParameterCollection), typeof(Model3D10),
            new PropertyMetadata(new PropertyChangedCallback(EffectParameters_PropertyChanged)));
        #endregion

        #region Dependency Properties Callbacks
        private static void Transform_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as Model3D10).OnTransformChanged(e);
        }

        private static void Effect_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as Model3D10).OnEffectChanged(e);
        }

        private static void EffectTechnique_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as Model3D10).OnEffectTechniqueChanged(e);
        }

        private static void EffectParameters_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as Model3D10).OnEffectParametersChanged(e);
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="Model3D10"/>
        /// </summary>
        protected Model3D10()
            : base()
        {
            this.Transform = Transform3D10.Identity;
            this.EffectParameters = new EffectParameterCollection();
        }
        #endregion

        #region Data
        private SlimDX.Direct3D10.EffectTechnique _technique;
        #endregion

        #region Public Properties
        /// <summary>
        /// Effet DirectX
        /// </summary>
        [TypeConverter(typeof(EffectTypeConverter))]
        [Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public SlimDX.Direct3D10.Effect Effect
        {
            get { return (SlimDX.Direct3D10.Effect)GetValue(EffectProperty); }
            set { SetValue(EffectProperty, value); }
        }

        /// <summary>
        /// Technique de rendu sélectionnée
        /// </summary>
        public string EffectTechnique
        {
            get { return (string)GetValue(EffectTechniqueProperty); }
            set { SetValue(EffectTechniqueProperty, value); }
        }

        /// <summary>
        /// Liste des paramètres à appliquer à l'effet
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227")]
        public EffectParameterCollection EffectParameters
        {
            get { return (EffectParameterCollection)GetValue(EffectParametersProperty); }
            set { SetValue(EffectParametersProperty, value); }
        }

        /// <summary>
        /// Obtient ou définit <see cref="Transform3D10"/> appliquée au modèle. 
        /// </summary>
        public Transform3D10 Transform 
        {
            get { return (Transform3D10)GetValue(TransformProperty); }
            set { SetValue(TransformProperty, value); }
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// En cas de substitution dans une classe dérivée, participe au dessin des objets dans le pipeline DirectX10
        /// </summary>
        /// <param name="context">Contexte de dessin.</param>
        /// <exception cref="ArgumentNullException">Soulevée si le contexte est null.</exception>
        protected virtual void OnDraw(DrawingContext context)
        {
            if (context == null) throw new ArgumentNullException("context");

            SlimDX.Matrix world = context.WorldMatrix * this.Transform.Value;
            context.Technique = _technique;

            foreach (IDrawable parameter in this.EffectParameters)
            {
                context.WorldMatrix = world;
                parameter.Draw(context);
            }

            context.WorldMatrix = world;
        }

        /// <summary>
        /// Appelé lors du changement de la transformation à appliquer à au <see cref="Model3D10"/>
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> décrivant les informations sur le changement.</param>
        protected virtual void OnTransformChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
        }

        /// <summary>
        /// Appelée lors du changement de l'effet
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> décrivant les informations sur le changement.</param>
        protected virtual void OnEffectChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (this.Effect !=  null)
            {
                this.EffectParameters.SetEffect(this.Effect);
                if (!string.IsNullOrEmpty(this.EffectTechnique)) 
                    this._technique = this.Effect.GetTechniqueByName(this.EffectTechnique);
            }
        }

        /// <summary>
        /// Appelé lors du changement de la technique sélectionnée
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> décrivant les informations sur le changement.</param>
        protected virtual void OnEffectTechniqueChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (!object.ReferenceEquals(this.Effect, null)
                && !object.ReferenceEquals(this.Effect, null))
                this._technique = this.Effect.GetTechniqueByName(this.EffectTechnique);
        }

        /// <summary>
        /// Appelé lors du changement de la liste des paramètres à appliquer à l'effet
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> décrivant les informations sur le changement.</param>
        protected virtual void OnEffectParametersChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
        }
        #endregion

        #region Protected Properties
        /// <summary>
        /// Obtient la technique courante
        /// </summary>
        protected internal SlimDX.Direct3D10.EffectTechnique CurrentEffectTechnique
        {
            get { return _technique; }
        }
        #endregion

        #region IDrawable Implementation
        /// <summary>
        /// Dessine un objet dans le pipeline.
        /// </summary>
        /// <param name="context">Contexte de dessin associé.</param>
        void IDrawable.Draw(DrawingContext context)
        {
            this.OnDraw(context);
        }
        #endregion
    }
}
